/**
 * memory.c
 *
 * @author  Philippe Johnson
 */

#include "common.h"
#include "memory.h"

/** BANK RANGE AND MEMORY MAP DESCRIPTION **/
typedef enum {
    ROMRANGE,    /* For banks which contain ROM data */
    RAMRANGE,    /* For banks which contain RAM data */
    MMIORANGE,   /* For MMIO banks with mapped registers */
    SRAMRANGE    /* For System RAM banks */
} RANGETYPE;

typedef enum {
    NONE,   /* Standard bank uniquely mapped to something */
    SHADOW  /* Shadowed bank. Refers to the same address as previous bank */
} BANKOPTION;

typedef struct _MapRange {
    u8         startBank;   /* Beginning of bank range     (inclusive)*/
    u8         endBank;     /* End of bank range           (inclusive)*/
    u8         startSubBank;/* Beginning of sub-bank range (inclusive)*/
    u8         endSubBank;  /* End of sub-bank range       (inclusive)*/
   /*u8         memSpeed;     Memory speed-- IGNORED FOR THE MOMENT  */
    RANGETYPE  type;
    BANKOPTION option;
} MapRange;

typedef struct _MemoryMap {
    unsigned int size;      // Number of BankRange structs
    MapRange*    ranges;    // The bank ranges describing the memory map
} MemoryMap;

/** FORWARD DECLARATIONS **/
void initializeMemoryMap( MemoryMap map, ROMTYPE type );
BOOL isTrapAddress( u32 address );

/** VISIBLE FUNCTIONS **/
void initializeMemory( CartridgeInfo* info ) {
    MemoryMap map;

    if( info->romType == LOROM ) {
        MapRange ranges[] = { 
        /* LowRAM */
        { 0x00, 0x3F, 0x0, 0x1, RAMRANGE ,   SHADOW },
        /* HW regs */
        { 0x00, 0x3F, 0x2, 0x7, MMIORANGE,   NONE },
        /* ROM */
        { 0x00, 0x3F, 0x8, 0xF, ROMRANGE ,   NONE },
        /* ROM */
        { 0x40, 0x7C, 0x0, 0xF, ROMRANGE ,   NONE },
    	/* SRAM */
    	{ 0x7D, 0x7D, 0x0, 0xF, SRAMRANGE,   NONE },
        /* LowRAM */
        { 0x7E, 0x7F, 0x0, 0xF, RAMRANGE ,   NONE } };
            
        map.size   = 6;
        map.ranges = ranges;

        /* No HIROM support yet */
        initializeMemoryMap( map, LOROM );
    } else if ( info->romType == HIROM ) {
        /* TODO: HiROM support */
        DEBUGMSG( "initializeMemory error: HiROM not yet supported\n" );
    } else {
        DEBUGMSG( "initializeMemory error: Invalid memory map type\n" );
    }
}

void uninitializeMemory() {
    free( snesRAM );
}

/** READ FUNCTIONS **/
u8 memRead8( u32 address ) {
    u8 retVal;

    if( isTrapAddress( address ) ) {
        retVal = mmioReadHandlers[ MMIOHASH( address ) ]( address, 8, 0 );
    } else {
        retVal = *( ( u8* )GETADDRESS( address ) );
    }

    return retVal;
}

u8 memRead8NoTrap( u32 address ) {
    return *( ( u8* )GETADDRESS( address ) );
}

u16 memRead16( u32 address ) {
    u16 retVal;

    if( isTrapAddress( address ) ) {
        retVal = mmioReadHandlers[ MMIOHASH( address ) ]( address, 16, 0 );
    } else {
        retVal = *( ( u16* )GETADDRESS( address ) );
    }

    return retVal;
}

u16 memRead16NoTrap( u32 address ) {
    return *( ( u16* )GETADDRESS( address ) );
}

u32 memRead24( u32 address ) {
    u32 retVal;

    if( isTrapAddress( address ) ) {
        retVal = mmioReadHandlers[ MMIOHASH( address ) ]( address, 24, 0 );
    } else {
        retVal = *( ( u32* )GETADDRESS( address ) ) & 0x00FFFFFF;
    }

    return retVal;
}

u32 memRead24NoTrap( u32 address ) {
    return *( ( u32* )GETADDRESS( address ) ) & 0x00FFFFFF;
}

/** WRITE FUNCTIONS **/
void memWrite8( u32 address, u8 value ) {
    if( isTrapAddress( address ) ) {
        mmioWriteHandlers[ MMIOHASH( address ) ]( address, 8, value );
    } else {
        *( ( u8* )GETADDRESS( address ) ) = value;
    }
}

void memWrite8NoTrap( u32 address, u8 value ) {
    *( ( u8* )GETADDRESS( address ) ) = value;
}

void memWrite16( u32 address, u16 value ) {
    if( isTrapAddress( address ) ) {
        mmioWriteHandlers[ MMIOHASH( address ) ]( address, 16, value );
    } else {
        *( ( u16* )GETADDRESS( address ) ) = value;
    }
}

void memWrite16NoTrap( u32 address, u16 value ) {
    *( ( u16* )GETADDRESS( address ) ) = value;
}

/**
 * initializeMemoryMap
 * Initializes the memory map based on the map information passed in the
 * MemoryMap structure. 
 *
 * @param map The memory map structure
 */
void initializeMemoryMap( MemoryMap map, ROMTYPE type ) {
    u8*  romPtr  = getRomData(); 
    u8*  ramPtr  = ( u8* )snesRAM;
    u8*  sramPtr = ( u8* )SRAM;
    u8** proxyPtr;                  /* So we only have one switch block */
    int  index;                     /* Index into memory map            */

    /* TODO: check type and change the map accordingly */

    unsigned int i;
    for( i = 0; i < map.size; ++i ) {
        /* For each bank range... */
        int bank      = map.ranges[ i ].startBank;
        int lastBank  = map.ranges[ i ].endBank;

        switch( map.ranges[ i ].type ) {
            case ROMRANGE:
                proxyPtr = &romPtr;
                break;
            case RAMRANGE:
                proxyPtr = &ramPtr;
                break;
            case SRAMRANGE:
                proxyPtr = &sramPtr; /* TODO: ERAM support */
                break;
            default:
                break;
        }

        for( ; bank <= lastBank; ++bank ) {
            /* For each bank... */
            int subBank   = map.ranges[ i ].startSubBank;
            int lastSBank = map.ranges[ i ].endSubBank;
            for( ; subBank <= lastSBank; ++subBank ) {
                /* For each sub bank... */
                index = ( bank << 4 ) | subBank;

                if( map.ranges[ i ].type == MMIORANGE ) {
                    memoryMap[ index ] = memoryMap[ index + 0x800 ] =
                        ( void* )MMIO_MARKER;
                } else {
                   u8* temp = *proxyPtr;
                   memoryMap[ index ] = memoryMap[ index + 0x800 ] = 
                    &temp[ 0x1000 * (subBank - map.ranges[ i ].startSubBank) ];
                }
            }

    	    if( map.ranges[ i ].option == NONE &&
	    	    map.ranges[ i ].type   != MMIORANGE ) {
                *proxyPtr += 0x1000 * (map.ranges[ i ].endSubBank - map.ranges[ i ].startSubBank + 1);
	        }
        }
    }
}

/**
 * isTrapAddress
 * Determines whether or not a read or write to a specific address
 * is within the hardware register range.
 *
 * @param address The address to check
 * @return TRUE if the address should be trapped, FALSE otherwise
 */
BOOL isTrapAddress( u32 address ) {
    int upperHalf = address & 0xFFF000;

    if( ( int )GETADDRESS( upperHalf ) == ( MMIO_MARKER ) ) {
        return TRUE;
    }

    return FALSE;
}

